using Game.Networking.Server;
using NaughtyAttributes;
using UnityEngine;

namespace Game.Networking {
	public abstract class WorldEntity<T> : Entity<T>, ISyncTransform where T : WorldEntityState, new() {
		const float POS_MIN_DELTA    = 0.03f;
		const float ANG_MIN_DELTA    = 1f;

		Vector3 _lastSentPos = Vector3.zero;
		Vector3 _lastSentDir = Vector3.zero;
		WorldEntityTransformInterpolator _interpolator = null;

		[EnumByteFlags]
		public WorldEntityFlags SyncFlags = WorldEntityFlags.SyncPosition;

		protected override T SaveInternal() {
			var state = base.SaveInternal();
			state.Position = transform.position;
			state.Rotation = transform.rotation.eulerAngles;
			state.SyncFlags= SyncFlags;
			return state;
		}

		public override void Create() {
			base.Create();
			if ( !IsServer ) {
				var rb = GetComponent<Rigidbody>();
				if ( rb ) {
					rb.isKinematic = true;
				}
				if ( IsFlagSet( WorldEntityFlags.Interpolate) ) {
					_interpolator = new WorldEntityTransformInterpolator(transform);
				}				
			}
		}

		public override void ApplyFullState(EntityState state) {
			base.ApplyFullState(state);
			var cast = (T)state;
			if (cast == null) {
				return;
			}
			transform.position = cast.Position;
			transform.eulerAngles = cast.Rotation;
			SyncFlags= cast.SyncFlags;
			if ( IsServer ) {
				_lastSentPos = cast.Position;
				_lastSentDir = cast.Rotation;
			}
		}

		public override void Tick(float currentTime, float deltaTime) {
			base.Tick(currentTime, deltaTime);
			if ( _interpolator!= null ) {
				_interpolator.Update(deltaTime);
			}
		}

		public override void NetTick() {
			base.NetTick();
			if ( SyncFlags == 0 ) {
				return;
			}
			TrySendPosUpdate();
		}

		public void ApplyPositionUpdate(Vector3 newPosition) {
			if ( _interpolator != null ) {
				_interpolator.UpdatePosition(newPosition, false);
			} else {
				transform.position = newPosition;
			}			
		}

		public void ApplyRotationUpdate(Vector3 newRotation) {
			if ( _interpolator != null ) {
				_interpolator.UpdateRotation(Quaternion.Euler(newRotation));
			} else {
				transform.rotation = Quaternion.Euler(newRotation);
			}
		}

		public Transform GetTransform() {
			return transform;
		}

		void TrySendPosUpdate() {
			var syncPosition = IsFlagSet(WorldEntityFlags.SyncPosition);
			var syncRotation = IsFlagSet(WorldEntityFlags.SyncRotation);
			var sc = ServerEntityController.Instance;
			if ( !sc.HasNetworkObserver(ID) ) {
				return;
			}

			var moveDelta = transform.position - _lastSentPos;
			var currentLook = transform.rotation.eulerAngles;

			var posChanged = syncPosition && moveDelta.magnitude > POS_MIN_DELTA;
			var rotChanged = syncRotation && Vector3.Distance(_lastSentDir, currentLook) > ANG_MIN_DELTA;

			if ( !posChanged && !rotChanged ) {
				return;
			}
			_lastSentPos = transform.position;
			_lastSentDir = currentLook;
			sc.SendEntityPositionUpdate(ID, transform.position, currentLook, posChanged, rotChanged);
		}

		bool IsFlagSet(WorldEntityFlags flag) {
			return (SyncFlags & flag) != 0;
		}
	}

	public interface ISyncTransform {
		public void ApplyPositionUpdate(Vector3 newPosition);
		public void ApplyRotationUpdate(Vector3 newRotation);
		public Transform GetTransform();
	}
}
